using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using BusinessFramework.DynamicProperties;
using BusinessFramework.Formula;

namespace BusinessFramework
{
    public class EntityExtensions : IEntityExtensions, INotifyPropertyChanged
    {
        protected EntityExtensions(object parent)
        {
            Target = parent;
        }

        public object Target { get; private set; }
        
        private IEntityExtensions _parent;
        public IEntityExtensions Parent
        {
            get { return _parent; }
            set
            {
                _parent = value;
                if (_parent == null) return;

                // Access exactly the base class
                if (_parent is IEE)
                    _parent = ((IEE) _parent).GetExtensions();
                
                if (_parent.RuleInitialized && !RuleInitialized)
                    InitializeRules();

                if (_parent.IsDirtyTrackingStarted)
                {
                    StartDirtyTracking();
                    _parent.IsDirty = true;
                }
                RaisePropertyChange("IsValid", true);
            }
        }
        
#if !PHONE
        public ICallProvider CompilerCallProvider { get; set; }
#endif
        #region Rules

        private RuleCollection _rules; 
        public RuleCollection Rules
        {
            get
            {
                if (_rules == null)
                    _rules = new RuleCollection(this, Initializer.DecorateRules);
                return _rules;
            }
        }

        private BrokenRuleCollection _brokenRules;
        public BrokenRuleCollection BrokenRules
        {
            get
            {
                if (_brokenRules == null)
                {
                    _brokenRules = new BrokenRuleCollection();

                    _brokenRules.RuleBroken += OnBrokenRuleChanged;
                    _brokenRules.RuleRepaired += OnBrokenRuleChanged;
                }
                return _brokenRules;
            }
        }

        void OnBrokenRuleChanged(object sender, BrokenRuleEventArgs e)
        {
            RaisePropertyChange("IsValid",true);
        }

        private DependencyNode _dependency;
        private DependencyNode Dependency
        {
            get
            {
                if (_dependency == null)
                {
                    _dependency = new DependencyNode();
                    _dependency.Subscribe(Target);
                }
                return _dependency;
            }
        }

        public void StartDirtyTracking()
        {
            SafeRecursiveAction(
                ex =>
                    {
                        var actionWithTag = new DependencyNode.ActionWithTag(s => ex.IsDirty = true, "");
                        ex.Dependency.AddPropertyDependency("*", actionWithTag);
                        ex.IsDirtyTrackingStarted = true;
                    }
                );
        }

        public void ClearIsDirty()
        {
            SafeRecursiveAction(ex => { ex.IsDirty = false; });
        }

        public bool IsDirtyTrackingStarted { get; private set; }

        private bool _isDirty;
        public bool IsDirty
        {
            get
            {
                bool isDirty = false;
                SafeRecursiveAction(ex => { isDirty |= ex._isDirty; }); 
                
                return isDirty;
            }
            set
            {
                if (_isDirty == value)
                    return;

                _isDirty = value;
                RaisePropertyChange("IsDirty", true);
            }
        }

        internal void OnRuleAdded(Rule rule)
        {
            rule.OnRuleAdded(this);
        }

        internal void OnRuleRemoved(Rule rule)
        {
            Dependency.RemoveDependency(rule.OnInternalDependencyChanged);
            rule.OnRuleRemoved();
        }

        internal void AddPropertyDependency(Rule rule, PropertyPath propertyPath, string tag)
        {
            var actionWithTag = new DependencyNode.ActionWithTag(rule.OnInternalDependencyChanged, tag);
            actionWithTag.Priority = () => rule.Priority;

            Dependency.AddPropertyDependency(propertyPath, actionWithTag);
        }

        private IExtensionOptions _initializer;

        protected IExtensionOptions Initializer
        {
            get
            {
                if (_initializer == null)
                    _initializer = ExtensionsOptions.GetExtensionsOptions(Target.GetType());

                return _initializer;
            }
            private set
            {
                _initializer = value;
            }
        }

        private bool _ruleInitialized;
        public bool RuleInitialized
        {
            get { return _ruleInitialized; }
            private set
            {
                if (_ruleInitialized == value)
                    return;

                _ruleInitialized = value;
                RaisePropertyChange("RuleInitialized", true);
            }
        }

        public void InitializeRules()
        {
            SafeRecursiveAction(e =>
                                    {
                                        if (e.RuleInitialized) return;

                                        e.Initializer.InitializeRules(e);
                                        e.RuleInitialized = true;
                                    });
        }


        internal void SafeRecursiveAction( Action<EntityExtensions> action, Dictionary<object, object> visited)
        {
            if (visited.ContainsKey(this))
                return;
            visited.Add(this, null);

            action(this);

            foreach (var childEntityExtension in Initializer.RecursiveStrategy.GetAllChildObjects(Target))
            {
                var entityExtension = (EntityExtensions)childEntityExtension;
                action(entityExtension);
                entityExtension.SafeRecursiveAction(action, visited);
            }
        }

        protected void SafeRecursiveAction( Action<EntityExtensions> action )
        {
            var visited = new Dictionary<object, object>(ObjectReferenceEqualityComparerer<object>.Default);
            SafeRecursiveAction(action, visited);
        }

        public bool IsValid
        {
            get
            {
                bool isValid = true;
                SafeRecursiveAction(e =>
                                        {
                                            isValid &= e.BrokenRules.Where(r => r.Severity == BrokenRuleSeverity.Error).
                                                           FirstOrDefault() == null;

                                        });

                return isValid;
            }
        }

        public bool Validate()
        {
            return Validate(ValidationMode.OnlyErrors);
        }

        public bool Validate(ValidationMode mode)
        {
            bool valid = true;

            SafeRecursiveAction(e =>
                                    {
                                        // Remove OneTime BrokenRules
                                        for (int i = 0; i < e.BrokenRules.Count; i++)
                                        {
                                            if (e.BrokenRules[i].IsOneTime)
                                            {
                                                e.BrokenRules.RemoveAt(i);
                                                i--;
                                            }
                                        }

                                        // Validate ValidationRules with Error severity
                        foreach (var rule in e.Rules.OfType<ValidationRuleBase>().Where(r => r.State == RuleState.Enabled))
                        {
                            if (mode == ValidationMode.OnlyErrors && rule.Severity != BrokenRuleSeverity.Error)
                                continue;

                            bool res = rule.Validate();
                            if (rule.Severity == BrokenRuleSeverity.Error)
                                valid &= res;
                        }
                    });

            return valid;
        }

        #endregion
        #region ExtendedProperties

        private DynamicPropertyCollection _dynamicProperties;
        public DynamicPropertyCollection DynamicProperties
        {
            get 
            {
                if (_dynamicProperties == null)
                {
                    _dynamicProperties = new DynamicPropertyCollection();
                    Initializer.InitializeProperties(this);
                }
                return _dynamicProperties; 
            }
        }
        
        #endregion

        public static IEntityExtensions RegisterObject(object target)
        {
            return RegisterObject(target, null);
        }

        public static IEntityExtensions RegisterObject(object target, IExtensionOptions initializer)
        {
            if (target == null) throw new ArgumentNullException("target");

            var entityExtensions = new EntityExtensions(target);
            entityExtensions.Initializer = initializer;

            var attribute = target.GetType().GetCustomAttributes(true).FirstOrDefault(x => x.GetType().Name == "SetParentAttribute");
            if (attribute == null)
                entityExtensions.InitRecursion();

            return entityExtensions;
        }

        private void InitRecursion()
        {
            if (!(Target is INotifyPropertyChanged)) return;
            
            ((INotifyPropertyChanged)Target).PropertyChanged += OnPropertyChanged;
            foreach (var childObject in Initializer.RecursiveStrategy.GetAllChildObjects(Target))
                childObject.Parent = this;

            foreach (var collections in Initializer.RecursiveStrategy.GetCollections(Target).OfType<INotifyCollectionChanged>())
                collections.CollectionChanged += OnCollectionChanged;
        }

        private readonly Dictionary<string, object> _objects = new Dictionary<string, object>();
        void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            object value;
            _objects.TryGetValue(e.PropertyName, out value);
            var propertyInfo = Target.GetType().GetProperty(e.PropertyName);

            if (propertyInfo.IsDefined(typeof(IgnoreTraversalAttribute), true))
                return;

            if (value != null)
            {
                if (value is IEntityBase)
                    ((IEntityBase) value).Extensions.Parent = null;
                if ( value is INotifyCollectionChanged)
                    ((INotifyCollectionChanged)value).CollectionChanged -= OnCollectionChanged;
            }

            var o = propertyInfo.GetValue(Target, null);
            if (o != null )
            {
                if ( o is IEntityBase)
                    ((IEntityBase)o).Extensions.Parent = this;

                if (value is INotifyCollectionChanged)
                    ((INotifyCollectionChanged)value).CollectionChanged += OnCollectionChanged;
            }
            
            _objects[e.PropertyName] = o;
        }

        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                ((IEntityBase) e.NewItems[0]).Extensions.Parent = this;
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                ((IEntityBase)e.OldItems[0]).Extensions.Parent = null;
            }
            else if (e.Action == NotifyCollectionChangedAction.Replace)
            {
                ((IEntityBase)e.OldItems[0]).Extensions.Parent = null;
                ((IEntityBase)e.NewItems[0]).Extensions.Parent = this;
            }
#if NET
            else if (e.Action == NotifyCollectionChangedAction.Move)
            {
                
            }
#endif
            else
            {
                throw new NotImplementedException();
            }
        }

        public static IEntityExtensions<T> RegisterTypedObject<T>(T target)
        {
            return RegisterObject(target, null).GetTypeSafe<T>();
        }

        public static IEntityExtensions<T> RegisterTypedObject<T>(T target, IExtensionOptions initializer)
        {
            return RegisterObject(target, initializer).GetTypeSafe<T>();
        }

        public void RaisePropertyChange(string propertyName)
        {
            RaisePropertyChange(propertyName, false);
        }

        public void RaisePropertyChange(string propertyName, bool notifyParent)
        {
            var h = PropertyChanged;
            if ( h != null)
                h(this, new PropertyChangedEventArgs(propertyName));

            if (notifyParent && Parent != null)
                ((EntityExtensions) Parent).RaisePropertyChange(propertyName, true);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public override string ToString()
        {
            if (Target != null)
                return "Target=" + Target;
            return base.ToString();
        }
    }

    interface IEE
    {
        IEntityExtensions GetExtensions();
    }

    public class EntityExtensions<T> : IEntityExtensions<T>, INotifyPropertyChanged, IEE
    {
        private readonly IEntityExtensions _extensions;

        public static implicit operator EntityExtensions<T>(EntityExtensions e)
        {
            return (EntityExtensions<T>)e.GetTypeSafe<T>();
        }

        internal EntityExtensions(IEntityExtensions extensions)
        {
            ((EntityExtensions)extensions).PropertyChanged += ExtensionsPropertyChanged;
            _extensions = extensions;
        }

        public RuleCollection Rules
        {
            get { return _extensions.Rules; }
        }

        public BrokenRuleCollection BrokenRules
        {
            get { return _extensions.BrokenRules; }
        }

        public bool IsValid
        {
            get { return _extensions.IsValid; }
        }

        public DynamicPropertyCollection DynamicProperties
        {
            get { return _extensions.DynamicProperties; }
        }

        public void StartDirtyTracking()
        {
            _extensions.StartDirtyTracking();
        }

        public bool IsDirty
        {
            get { return _extensions.IsDirty; }
            set { _extensions.IsDirty = value; }
        }
        public bool IsDirtyTrackingStarted
        {
            get { return _extensions.IsDirtyTrackingStarted; }
        }

        public void ClearIsDirty()
        {
            _extensions.ClearIsDirty();
        }

        public bool Validate()
        {
            return _extensions.Validate();
        }

        public bool Validate(ValidationMode mode)
        {
            return _extensions.Validate(mode);
        }

        public bool RuleInitialized
        {
            get { return _extensions.RuleInitialized; }
        }

        public void InitializeRules()
        {
            _extensions.InitializeRules();
        }
#if !PHONE
        public ICallProvider CompilerCallProvider
        {
            get { return _extensions.CompilerCallProvider; }
            set { _extensions.CompilerCallProvider = value; }
        }
#endif
        public object Target
        {
            get { return _extensions.Target; }
        }

        public IEntityExtensions Parent
        {
            get { return _extensions.Parent; }
            set { _extensions.Parent = value; }
        }

        public IEntityExtensions GetExtensions()
        {
            return _extensions;
        }

        void ExtensionsPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var p = PropertyChanged;
            if (p != null)
                p(this, e);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}